/* IP (Layer 3 only) implementation  */

/* Copyright (C) 2009-2011 David Zanetti
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */


#include <stdio.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include "global.h"
#include "ip.h"
#include "serial.h"
#include "checksum.h"
#include "icmp.h"
#include "udp.h"
#include "ether.h"
#include "arp.h"
#include "encx24j600.h"
#include <string.h>
#include <avr/interrupt.h>
#include <util/atomic.h>

uint8_t EEMEM ip_addr_eeprom[4];

typedef struct {
		uint8_t ver;
		uint8_t tos;
		uint8_t len[2];
		uint8_t id[2];
		uint8_t flag;
		uint8_t frag;
		uint8_t ttl;
		uint8_t protocol;
		uint8_t headercrc[2];
		uint8_t src[4];
		uint8_t dest[4];
} ip_packet_t; /* covers header only */

/* process an IP packet */
/* returns non-zero on there being some data in the slot to send */
uint8_t ip_process(uint8_t slot) {
	ip_packet_t pkt, tx;
	uint16_t calc_crc, n;
	uint8_t ret = E_NOTHING;

	/* fill our header information */
	if (encx24j600_rx(&pkt,sizeof(ip_packet_t)) != sizeof(ip_packet_t)) {
		serial0_tx_PGM(PSTR("ip: short frame, discarding\r\n"));
		return E_NOTHING;
	}

#ifdef DEBUG_IP
	serial0_tx_PGM(PSTR("ip: rx v"));
	serial0_tx_dec(pkt.ver >> 4);
	serial0_tx_cout(' ');
	serial0_tx_dec(pkt.src[0]);
	serial0_tx_cout('.');
	serial0_tx_dec(pkt.src[1]);
	serial0_tx_cout('.');
	serial0_tx_dec(pkt.src[2]);
	serial0_tx_cout('.');
	serial0_tx_dec(pkt.src[3]);
	serial0_tx_PGM(PSTR("->"));
	serial0_tx_dec(pkt.dest[0]);
	serial0_tx_cout('.');
	serial0_tx_dec(pkt.dest[1]);
	serial0_tx_cout('.');
	serial0_tx_dec(pkt.dest[2]);
	serial0_tx_cout('.');
	serial0_tx_dec(pkt.dest[3]);
	serial0_tx_PGM(PSTR(" p:"));
	serial0_tx_dec(pkt.protocol);
	serial0_tx_PGM(PSTR(" l:"));
	serial0_tx_hex(pkt.len[0]);
	serial0_tx_hex(pkt.len[1]);
	serial0_tx_PGM(PSTR("\r\n"));
#endif

	/* discard extra options, we don't handle them at all */
	if ((pkt.ver & 0xf) > 5) {
		uint32_t discard;
		uint8_t m;
		for (m = 0; m < ((pkt.ver & 0xf)-5); m++) {
			encx24j600_rx(&discard,sizeof(uint32_t));
		}
	}

	/* compute checksum of the header */
	calc_crc = encx24j600_rx_crc((pkt.ver & 0xf)*4);
	if (calc_crc != 0xffff && calc_crc != 0x0000) {
#ifdef DEBUG_IP
		serial0_tx_PGM(PSTR("ip: header checksum wrong, discarding\r\n"));
#endif
		return E_NOTHING;
	}

	/* FIXME: we should really check if we have a configured IP address
    and if we're getting something sane, or broadcast for DHCP */

	switch (pkt.protocol) {
		case 1:
			/* Internet Control Message Protocol */
			ret = icmp_process(slot,
					((pkt.len[0] << 8 | pkt.len[1])-(pkt.ver & 0xf)*4));
			break;
		case 17:
			/* User Datagram Protocol */
			ret = udp_process(slot,pkt.src,pkt.dest);
			break;
		default:
			break;
	}

	/* if we have a packet */
	if (ret) {
		uint16_t len;

		/* find length of the payload in the slot */
		len = encx24j600_slot_len(slot);

		/* preapre and write out our header */
		tx.ver = (4 << 4) | 5; /* version 4, 5 32-bit headers in total */
		tx.tos = 0;
		tx.len[0] = ((len+sizeof(ip_packet_t)) >> 8); /* length we got from upper layer + header */
		tx.len[1] = ((len+sizeof(ip_packet_t)) & 0xff);;
		tx.id[0] = 0;
		tx.id[1] = 0;
		tx.flag = 0x40; /* DF bit set */
		tx.frag = 0; /* we don't use either of those */
		tx.ttl = 0x80; /* many hops */
		/* FIXME: allow ICMP in response */
		tx.protocol = pkt.protocol; /* probably! */
		tx.headercrc[0] = 0;
		tx.headercrc[1] = 0;
		if (ret == E_REPLY_BROAD) {
			/* use broadcast IP address for dest */
			for (n = 0; n < 4; n++) {
				tx.src[n] = ip_addr[n];
				tx.dest[n] = 0xff;
			}
		} else {
			/* use orignating IP address as dest */
			for (n = 0; n < 4; n++) {
				tx.src[n] = ip_addr[n];
				tx.dest[n] = pkt.src[n];
			}
		}
		/* prepend our header to the packet now */
		if (!encx24j600_tx_prepend(slot,(uint8_t *)&tx,sizeof(ip_packet_t))) {
			/* ah, well, we have to discard this now! */
			return E_NOTHING;
		}
		/* perform CRC calculation on whole header now */
		calc_crc = encx24j600_tx_crc_slot(slot,sizeof(ip_packet_t),0);
		//  calc_crc = encx24j600_tx_crc(addr,sizeof(ip_packet_t),0);
		/* write that into the packet */
		encx24j600_tx_write_slot(slot,calc_crc & 0xff,10);
		encx24j600_tx_write_slot(slot,calc_crc >> 8,11);

#ifdef DEBUG_IP
		serial0_tx_PGM(PSTR("ip: tx v"));
		serial0_tx_dec(tx.ver >> 4);
		serial0_tx_cout(' ');
		serial0_tx_dec(tx.src[0]);
		serial0_tx_cout('.');
		serial0_tx_dec(tx.src[1]);
		serial0_tx_cout('.');
		serial0_tx_dec(tx.src[2]);
		serial0_tx_cout('.');
		serial0_tx_dec(tx.src[3]);
		serial0_tx_PGM(PSTR("->"));
		serial0_tx_dec(tx.dest[0]);
		serial0_tx_cout('.');
		serial0_tx_dec(tx.dest[1]);
		serial0_tx_cout('.');
		serial0_tx_dec(tx.dest[2]);
		serial0_tx_cout('.');
		serial0_tx_dec(tx.dest[3]);
		serial0_tx_PGM(PSTR(" p:"));
		serial0_tx_dec(tx.protocol);
		serial0_tx_PGM(PSTR(" l:"));
		serial0_tx_hex(tx.len[0]);
		serial0_tx_hex(tx.len[1]);
		serial0_tx_PGM(PSTR("\r\n"));
#endif

		/* our packet is good, please be nice to it! */
		return ret; /* return the hint we got from the upper layer */
	}

	return E_NOTHING;
}


void ip_printaddr(uint8_t *ip_addr) {
	serial0_tx_dec(ip_addr[0]);
	serial0_tx_cout('.');
	serial0_tx_dec(ip_addr[1]);
	serial0_tx_cout('.');
	serial0_tx_dec(ip_addr[2]);
	serial0_tx_cout('.');
	serial0_tx_dec(ip_addr[3]);
}

void ip_init(void) {
	eeprom_read_block((void *) &ip_addr, (const void *) &ip_addr_eeprom, 4);
	/* report the IP address we have */
	if (!(ip_addr[0] == 0 &&
			ip_addr[1] == 0 &&
			ip_addr[2] == 0 &&
			ip_addr[3] == 0 )) {
		/* we are not trying to autoconfig */
		serial0_tx_PGM(PSTR("ip: local address is "));
		ip_printaddr(ip_addr);
		serial0_tx_PGM(PSTR("\r\n"));
		set_flag(flag_ip,FLAG_IP_CONFIG);
	} else {
		set_flag(flag_ip,FLAG_IP_DHCP);
		serial0_tx_PGM(PSTR("ip: using dhcp\r\n"));
	}
	/* this is as good a time as any, make sure our arp cache is clean */
	arp_init();
}

void ip_update(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
	ip_addr[0] = a;
	ip_addr[1] = b;
	ip_addr[2] = c;
	ip_addr[3] = d;
	eeprom_write_block((const void *) &ip_addr, (void *) &ip_addr_eeprom, 4);
}

/* our defaults */
void ip_eeprom_init(void) {
	uint8_t addr[4];

	addr[0] = IP_DEFAULT_1;
	addr[1] = IP_DEFAULT_2;
	addr[2] = IP_DEFAULT_3;
	addr[3] = IP_DEFAULT_4;
	eeprom_write_block((const void *) &addr, (void *) &ip_addr_eeprom, 4);
}

/* Send a given slot to the specific IP address given. This populates our source address automatically. */
uint8_t ip_send(uint8_t *dest_ip, uint8_t slot, uint8_t protocol) {
	ip_packet_t tx;
	uint8_t dest_mac[6], n;
	uint16_t len, calc_crc;

	/* obtain length from the slot */
	len = encx24j600_slot_len(slot);

	/* populate our header */
	tx.ver = (4 << 4) | 5; /* version 4, 5 32-bit headers in total */
	tx.tos = 0;
	tx.len[0] = ((len+sizeof(ip_packet_t)) >> 8); /* length we got from upper layer + header */
	tx.len[1] = ((len+sizeof(ip_packet_t)) & 0xff);;
	tx.id[0] = 0;
	tx.id[1] = 0;
	tx.flag = 0x40; /* DF bit set */
	tx.frag = 0; /* we don't use either of those */
	tx.ttl = 0x80; /* many hops */
	tx.protocol = protocol; /* as we were passed */
	tx.headercrc[0] = 0;
	tx.headercrc[1] = 0;
	for (n = 0; n < 4; n++) {
		tx.src[n] = ip_addr[n];
		tx.dest[n] = dest_ip[n];
	}
	/* prepend our header to the packet now */
	if (!encx24j600_tx_prepend(slot,(uint8_t *)&tx,sizeof(ip_packet_t))) {
		/* ah, well, we have to discard this now! */
		return 0;
	}
	/* perform CRC calculation on whole header now */
	calc_crc = encx24j600_tx_crc_slot(slot,sizeof(ip_packet_t),0);

	/* write that into the packet */
	encx24j600_tx_write_slot(slot,calc_crc & 0xff,10);
	encx24j600_tx_write_slot(slot,calc_crc >> 8,11);

	/* resolve MAC address for the send */

	/* if dest IP is global broadcast (255.255.255.255) then dest MAC is global broadcast too */

	if (dest_ip[0] == 0xff && dest_ip[1] == 0xff && dest_ip[2] == 0xff && dest_ip[3] == 0xff) {
		memset(dest_mac,0xff,6); /* use global ethernet MAC address */
	} else {
		uint8_t ret;
		/* resolve the MAC address via the ARP table */
		ret = arp_resolve(slot,dest_ip,dest_mac); /* overwrites dest mac */
		if (ret == E_NOTHING) {
			return E_NOTHING; /* we couldn't resolve it, and TX failed, so give up */
		}
		if (ret == E_NEEDS_ARP) {
			/* the slot is now in the hands of the ARP layer. return the hint */
#ifdef DEBUG_IP
			serial0_tx_PGM(PSTR("ip: needs arp\r\n"));
#endif
			return E_NEEDS_ARP;
		}
		/* only case which gets here will be E_RESOLVED, check it */
		if (ret != E_RESOLVED) {
			serial0_tx_PGM(PSTR("ip: BUG: arp resolve returned unknown result, giving up\r\n"));
			return E_NOTHING;
		}
	}

	/* now pass the packet down a layer and get it sent! */
	/* 0x0800 is IPv4 */
	if(ether_send(dest_mac,0x0800,slot)) {
		return 1;
	}

	return 0;
}
